﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;

namespace PocoGen.Common
{
    /// <summary>
    /// Represents a table in a database.
    /// </summary>
    public class Table : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Creates a new table.
        /// </summary>
        /// <param name="name">The table's name.</param>
        /// <param name="schema">The table's schema. Can be String.Empty if the database doesn't support schemas.</param>
        public Table(string schema, string name)
        {
            if (schema == null)
            {
                throw new ArgumentNullException("schema");
            }

            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("name is null or empty.", "name");
            }

            this.Columns = new ColumnCollection();
            this.ParentForeignKeys = new ForeignKeyCollection();
            this.ChildForeignKeys = new ForeignKeyCollection();
            this.GeneratedClassName = string.Empty;
            this.Ignore = false;
            this.IsView = false;
            this.Name = name;
            this.Schema = schema;
            this.SequenceName = string.Empty;
        }

        /// <summary>
        /// Creates a new table.
        /// </summary>
        /// <param name="schema">The table's schema. Can be String.Empty if the database doesn't support schemas.</param>
        /// <param name="name">The table's name.</param>
        /// <param name="isView">true if this is a view, false if this is a table.</param>
        public Table(string schema, string name, bool isView)
            : this(schema, name)
        {
            this.IsView = isView;
        }

        /// <summary>
        /// Gets the list of columns.
        /// </summary>
        public ColumnCollection Columns { get; private set; }

        /// <summary>
        /// Gets the list of foreign keys which are referenced by this table.
        /// </summary>
        public ForeignKeyCollection ParentForeignKeys { get; private set; }

        /// <summary>
        /// Gets the list of foreign keys which reference this table.
        /// </summary>
        public ForeignKeyCollection ChildForeignKeys { get; private set; }

        /// <summary>
        /// Gets the table's name.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the table's schema. Can be String.Empty if the database doesn't support schemas.
        /// </summary>
        public string Schema { get; private set; }

        private bool isView;
        /// <summary>
        /// Gets or sets whether this class is a view or table.
        /// </summary>
        public bool IsView
        {
            get
            {
                return this.isView;
            }
            set
            {
                if (this.isView == value)
                {
                    return;
                }

                this.isView = value;
                this.OnPropertyChanged();
            }
        }

        private string generatedClassName;
        /// <summary>
        /// Gets or sets the class name which was generated by the table name generators.
        /// </summary>
        public string GeneratedClassName
        {
            get
            {
                return this.generatedClassName;
            }
            set
            {
                if (this.generatedClassName == value)
                {
                    return;
                }

                this.generatedClassName = value;
                this.OnPropertyChanged();
                this.OnPropertyChanged(nameof(this.EffectiveClassName));
            }
        }

        private string userChangedClassName;
        /// <summary>
        /// Gets or sets the effective class name. This is the <see cref="GeneratedClassName"/> if the user didn't change it, otherwise it is the user-changed class name.
        /// </summary>
        public string EffectiveClassName
        {
            get
            {
                // If the user changed the class name, it was saved to userChangedClassName. Otherwise it is the GeneratedClassName.
                return this.userChangedClassName ?? this.GeneratedClassName;
            }
            set
            {
                if (this.EffectiveClassName == value)
                {
                    return;
                }

                // If the user changes the class name, save the override in userChangedClassName.
                if (this.GeneratedClassName != value)
                {
                    this.userChangedClassName = value;
                }
                else
                {
                    this.userChangedClassName = null;
                }
                this.OnPropertyChanged();
                this.OnPropertyChanged(nameof(this.UserChangedClassName));
            }
        }

        /// <summary>
        /// Gets the changed class name if the user changed it, otherwise null.
        /// </summary>
        public string UserChangedClassName
        {
            get
            {
                return this.userChangedClassName;
            }
        }

        private string sequenceName;
        /// <summary>
        /// Gets or sets the table's sequence name.
        /// </summary>
        public string SequenceName
        {
            get
            {
                return this.sequenceName;
            }
            set
            {
                if (this.sequenceName == value)
                {
                    return;
                }

                this.sequenceName = value;
                this.OnPropertyChanged();
            }
        }

        private bool ignore;
        /// <summary>
        /// Gets or sets whether this column should be included in the POCO.
        /// </summary>
        public bool Ignore
        {
            get
            {
                return this.ignore;
            }
            set
            {
                if (this.ignore == value)
                {
                    return;
                }

                this.ignore = value;
                this.OnPropertyChanged();
            }
        }

        /// <summary>
        /// Returns a list of columns which compose the primary key.
        /// </summary>
        /// <returns>A list of columns which compose the primary key.</returns>
        public ColumnCollection GetPrimaryKeyColumns()
        {
            ColumnCollection primaryKey = new ColumnCollection();
            primaryKey.AddRange(this.Columns.Where(c => !c.Ignore && c.IsPK));

            return primaryKey;
        }

        /// <summary>
        /// Returns whether a list of columns is the complete primary key and doesn't contain columns which aren't part of the primary key.
        /// </summary>
        /// <param name="columnNames">The list of column names.</param>
        /// <returns>true if the columns are the primary key, otherwise false.</returns>
        public bool IsPrimaryKey(IEnumerable<string> columnNames)
        {
            if (columnNames == null)
            {
                throw new ArgumentNullException("columnNames", "columnNames is null.");
            }

            List<string> columnNamesList = columnNames.ToList();
            ColumnCollection primaryKeyColumns = this.GetPrimaryKeyColumns();

            if (columnNamesList.Count != primaryKeyColumns.Count)
            {
                return false;
            }

            return primaryKeyColumns.All(pkc => columnNamesList.Contains(pkc.Name));
        }

        private void OnPropertyChanged([CallerMemberName]string propertyName = "")
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}